<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>响应式系统实现</title>
  </head>
  <body></body>
  <script>
    // 使用WeakMap存储目标对象与其依赖图的映射
    // WeakMap的键是对象，值是一个Map，这样当对象不再被引用时可以自动垃圾回收
    let targetMap = new WeakMap();
    
    // 当前正在运行的effect函数
    let activeEffect = null;

    /**
     * 注册effect函数
     * @param {Function} eff - 需要注册的effect函数
     */
    function watchEffect(eff) {
      activeEffect = eff; // 设置当前活跃的effect
      activeEffect(); // 立即执行effect函数，触发依赖收集
      activeEffect = null; // 执行完成后清除当前活跃的effect
    }

    /**
     * 跟踪依赖关系
     * @param {Object} target - 目标对象
     * @param {String} key - 属性键
     */
    function track(target, key) {
      if (activeEffect) {
        // 获取目标对象的依赖图
        let depsMap = targetMap.get(target);
        if (!depsMap) {
          // 如果没有依赖图，创建一个新的Map
          targetMap.set(target, (depsMap = new Map()));
        }

        // 获取属性的依赖集合
        let dep = depsMap.get(key);
        if (!dep) {
          // 如果没有依赖集合，创建一个新的Set
          depsMap.set(key, (dep = new Set()));
        }
        // 将当前活跃的effect添加到依赖集合中
        dep.add(activeEffect);
      }
    }

    /**
     * 触发更新
     * @param {Object} target - 目标对象
     * @param {String} key - 属性键
     */
    function trigger(target, key) {
      // 获取目标对象的依赖图
      let depsMap = targetMap.get(target);
      if (!depsMap) return; // 如果没有依赖图，直接返回

      // 获取属性的依赖集合
      let dep = depsMap.get(key);
      if (dep) {
        // 执行所有依赖该属性的effect函数
        dep.forEach((effect) => {
          effect();
        });
      }
    }

    /**
     * 创建响应式对象
     * @param {Object} target - 目标对象
     * @returns {Proxy} 响应式代理对象
     */
    function reactive(target) {
      const handler = {
        // 拦截get操作
        get(target, key, receiver) {
          console.log("get", key);
          // 使用Reflect获取原始值
          let result = Reflect.get(target, key, receiver);
          // 跟踪依赖
          track(target, key);
          return result;
        },
        // 拦截set操作
        set(target, key, value, receiver) {
          console.log("set", key);
          let oldValue = target[key];
          // 使用Reflect设置新值
          let result = Reflect.set(target, key, value, receiver);
          // 只有当值发生变化时才触发更新
          if (oldValue != value) {
            trigger(target, key);
          }
          return result;
        },
      };
      // 返回代理对象
      return new Proxy(target, handler);
    }

    /**
     * 创建响应式引用
     * @param {*} raw - 原始值
     * @returns {Object} 响应式引用对象
     */
    function ref(raw) {
      let r = {
        get value() {
          // 访问value属性时跟踪依赖
          track(r, "value");
          return raw;
        },
        set value(newVal) {
          // 只有当值变化时才触发更新
          if (newVal != raw) {
            raw = newVal;
            trigger(r, "value");
          }
        },
      };
      return r;
    }

    /**
     * 创建计算属性
     * @param {Function} getter - 计算函数
     * @returns {Object} 响应式引用对象
     */
    function computed(getter) {
      // 创建一个ref来存储计算结果
      let result = ref();
      // 使用watchEffect自动更新计算结果
      watchEffect(() => {
        result.value = getter();
      });
      return result;
    }

    // ==================== 使用案例 ====================

    // 1. reactive基本使用
    console.log("=== reactive基本使用 ===");
    const state = reactive({ count: 0 });

    watchEffect(() => {
      console.log("count is:", state.count);
    });

    state.count++;

    // 2. ref基本使用
    console.log("\n=== ref基本使用 ===");
    const num = ref(10);

    watchEffect(() => {
      console.log("num is:", num.value);
    });

    num.value = 20;

    // 3. computed基本使用
    console.log("\n=== computed基本使用 ===");
    const double = computed(() => state.count * 2);

    watchEffect(() => {
      console.log("double is:", double.value);
    });

    state.count++;

    // 4. 多个依赖
    console.log("\n=== 多个依赖 ===");
    const user = reactive({ name: "Alice", age: 25 });

    watchEffect(() => {
      console.log(`User: ${user.name}, ${user.age} years old`);
    });

    user.name = "Bob";
  </script>
</html>
